package cn.ibizlab.core.authentication.aspect;

import cn.ibizlab.core.authentication.domain.AuthLogin;
import cn.ibizlab.core.authentication.domain.AuthUserLogin;
import cn.ibizlab.core.authentication.service.impl.AuthenticationService;
import cn.ibizlab.util.enums.Entities;
import cn.ibizlab.util.errors.BadRequestAlertException;
import cn.ibizlab.util.service.SimpleAuditService;
import cn.ibizlab.util.web.IPUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.core.annotation.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.security.core.AuthenticationException;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import java.sql.Timestamp;
import java.util.Date;

/**
 * 统计登录失败次数切面类：统计用户登录失败次数，超过最大失败次数后，锁定登录账户。
 */
@Slf4j
@Aspect
@Component
@Order(200)
@ConditionalOnExpression("'${ibiz.auth.accountLock.enable:false}'.equals('true')")
public class SysAuthLockAspect {

    @Value("${ibiz.auth.accountLock.maxFailCount:5}")
    private int maxFailCount;

    @Value("${spring.cache.redis.time-to-live:3600}")
    private long timeToLive;

    @Autowired
    SimpleAuditService simpleAuditService;

    @Autowired
    private AuthenticationService authenticationService;

    @Around(value ="execution(* cn.ibizlab.core.authentication.service.AuthLoginService.login(..))")
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        if(!ObjectUtils.isEmpty(args) && args[0] instanceof AuthLogin){
            AuthLogin authLogin = (AuthLogin) args[0];
            String loginName = !ObjectUtils.isEmpty(authLogin.getDc())? String.format("%1$s|%2$s",authLogin.getLoginName(),authLogin.getDc()) : authLogin.getLoginName();
            //校验登录账户是否已锁定
            AuthUserLogin authUserLogin =  authenticationService.getAuthUserLogin(loginName);
            if(authUserLogin != null ) {
                long waitForUnlock = authUserLogin.getWaitForUnlock(maxFailCount,timeToLive);
                if (waitForUnlock > 0)
                    throw new BadRequestAlertException("登录失败次数过多，账号已被锁定，请等待"+waitForUnlock+"分钟或联系管理员解锁", Entities.AUTH_LOGIN.toString(), "SysAuthLock");
            }

            //未锁定，正常登录
            Object result;
            try {
                result = joinPoint.proceed();
            } catch (AuthenticationException e) {
                //登录失败，统计失败次数
                log.error(String.format("登录认证失败：%1$s，正在执行用户失败次数统计",e.getMessage()));
                int failCount = authUserLogin == null? 1 : authUserLogin.getFailCount() +1 ;

                authUserLogin = new AuthUserLogin().setLoginName(loginName).setLoginTime(new Timestamp(System.currentTimeMillis())).setFailCount(failCount).setIpAddress(IPUtils.getIpAddr(null));
                //将失败次数记入缓存
                authenticationService.putAuthUserLogin(loginName, authUserLogin);
                long waitForUnlock = authUserLogin.getWaitForUnlock(maxFailCount,timeToLive);
                if (waitForUnlock > 0)
                    throw new BadRequestAlertException("登录失败次数过多，账号已被锁定，请等待"+waitForUnlock+"分钟或联系管理员解锁",Entities.AUTH_LOGIN.toString(), "SysAuthLock");
                else
                    throw new BadRequestAlertException(String.format("用户名或密码错误，还剩[%1$s]次机会", maxFailCount - failCount),Entities.AUTH_LOGIN.toString(), "SysAuthLock");
            }

            //登录成功，清除缓存
            if(result != null && authUserLogin != null)
                authenticationService.resetAuthUserLogin(loginName);

            return result;
        }
        return joinPoint.proceed();
    }

}
